
/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/resources.hh"
#include "carina/common/exception.hh"
#include "carina/common/global.hh"
#include "carina/common/logging.hh"
#include "carina/ces-loader.hh"
#include "carina/image.hh"

#include <fstream>
#include <cstdlib>
#include <algorithm>

namespace Carina
{
void ResourceCache::addResourcePath(const Path& p)
{
    Directory dir;
    m_ResPaths.push_back(p);
    CE_ASSERT(dir.open(p), ("Directory does not exists: " + p.get()).c_str());
    for(Directory::iterator iter = dir.begin(); iter != dir.end(); ++iter)
    {
        if(iter->isDirectory()) // no recursion for now
            continue;
        string filename(iter->filename());
        m_ResMap[filename] = *iter;
    }
}

void ResourceCache::removeResourcePath(const Path& name)
{
    // Remove this resource and expect that it should be on the list.
    // It is not like impossible to get two events about some directory being added and then deleted.
    auto iter = std::find(m_ResPaths.begin(), m_ResPaths.end(), name);
    CE_ASSERT(iter != m_ResPaths.end(), "Unknown resource path: " + name.get());
    if(iter == m_ResPaths.end())
        return;
    string dir = name.get();
    // Remove all resources associated with this path
    for(auto i = m_ResMap.begin(); i != m_ResMap.end(); ++i)
        if(i->second.directoryPath() == dir)
            m_ResMap.erase(i);            
}

Path ResourceCache::getResourcePath(size_t idx) const
{
    return m_ResPaths[idx];
}

size_t ResourceCache::getResourcePathCount() const
{
    return m_ResPaths.size();
}

void ResourceCache::addResource(const Path& name)
{
    // Insert only if it is a unique name. Currently, that's just a limitation of the whole system.
    string filename = name.filename();
    auto& ref = m_ResMap[filename];
    CE_ASSERT(!ref.isValid(), "Duplication of resource: " + filename + ". Previous path: " + ref.get() + ". Current path: " + name.get());
    if(ref.isValid())
        return;
    ref = name;
}

void ResourceCache::removeResource(const Path& name)
{
    string filename = name.filename();
    auto iter = m_ResMap.find(filename);
    CE_ASSERT(iter != m_ResMap.end(), "Attempt to delete resource that is not currently on the list: " + name.get());
    CE_ASSERT(std::find(m_ResPaths.begin(), m_ResPaths.end(), name) == m_ResPaths.end(), "Trying to delete a resource path");
    m_ResMap.erase(iter);
}

string ResourceCache::getResource(const string& name) const
{
    FileMap::const_iterator i = m_ResMap.find(name);
	CE_ASSERT(i != m_ResMap.end(), "Unknown resource: " + name);
	return i == m_ResMap.end() ? "" : i->second.get();
}

string ResourceCache::getResourceRelative(const string& path) const
{
    for(size_t i = 0; i < m_ResPaths.size(); ++i)
    {
        string rel = m_ResPaths[i].relativePath(path);
        if(!rel.empty())
            return rel;
    }
    return string();
}

ResourceManager::ResourceManager(const RendererPtr& renderer, const ResourceCachePtr& rscache)
    :   m_Renderer(renderer),
        m_ResourceCache(rscache)
{
    m_MeshLoaders[".cem"] = make_aligned_shared<CEMLoader>();
    m_MaterialManager = make_aligned_unique<MaterialManager>(*this);
    
#ifdef CE_RELOADABLE_RESOURCES
    for(size_t i = 0; i < m_ResourceCache->getResourcePathCount(); ++i)
        m_Poller.addWatch(m_ResourceCache->getResourcePath(i));
    m_Events.reserve(128);
#endif
}

#ifdef CE_RELOADABLE_RESOURCES
void ResourceManager::updateResources()
{
    m_Events.clear();
    m_Poller.poll(m_Events);

    for(auto& evt : m_Events)
    {
        if(evt.type & (CE_FS_EVENT_CREATE|CE_FS_EVENT_MOVED_TO))
            m_ResourceCache->addResource(evt.name);
        if(evt.type & CE_FS_EVENT_MODIFY)
        {
            auto iter = m_ReloadCallbacks.find(evt.name);
            if(iter != m_ReloadCallbacks.end() && 
               !iter->second(std::cref(evt.name)))
            {
                Log::stream(CE_LOG_INFO) << "Removing resource modification callback for the following file: " << evt.name << std::endl;
                m_ReloadCallbacks.erase(iter);
            }
        }
        if(evt.type & (CE_FS_EVENT_MOVED_FROM|CE_FS_EVENT_DELETE))
            m_ResourceCache->removeResource(evt.name);
        if(evt.type & (CE_FS_EVENT_MOVE_SELF|CE_FS_EVENT_DELETE_SELF))
            m_ResourceCache->removeResourcePath(evt.name);
    }
}

string ResourceManager::getResource(const string& name, ReloadCallback reload_callback)
{
    string res = getResource(name);
    m_ReloadCallbacks[res] = reload_callback;
    return res;
}
#endif

void ResourceManager::getMaterial(Material& mat, const string& name)
{
    return m_MaterialManager->getMaterial(mat, name);
}

ShaderProgramPtr ResourceManager::getShaderProgram(const string& str)
{
    return m_MaterialManager->getShaderProgram(str);
}

TexturePtr ResourceManager::getTexture(const string& str)
{
    assert(m_Renderer.get());
    TexturePtr& tex = m_TexCache[str];
    if(!tex.get())
    {
        ImagePtr img = getImage(str);
		CE_ASSERT(img, "Could not load the following texture: " + str);
        if(!img)
			return TexturePtr();
        VarType format = img->format();
        size_t  width = img->width(),
                height = img->height();
        if(is_not_pow_2(width) || is_not_pow_2(height))
            THROW_EXCEPTION("unsupported image width/height: " + str);
        if(format == VT_UNKNOWN)
            THROW_EXCEPTION("unsupported file format: " + str);
        tex = m_Renderer->createTexture(TextureDescription(img->width(), img->height(), 1, format), img->getData(), CE_GENERATE_MIPS);
    }
    return tex;
}

ImagePtr ResourceManager::getImage(const string& str)
{
    return m_ImageManager.load(m_ResourceCache->getResource(str));
}

string ResourceManager::getResource(const string& name) const
{
    return m_ResourceCache->getResource(name);
}

string ResourceManager::getResourceRelative(const string& path) const
{
    return m_ResourceCache->getResourceRelative(path);
}

Path ResourceManager::getResourcePath(size_t idx) const
{
    return m_ResourceCache->getResourcePath(idx);
}

size_t ResourceManager::getResourcePathCount() const
{
    return m_ResourceCache->getResourcePathCount();
}

MeshPtr ResourceManager::getMesh(const string& str)
{
    assert(m_Renderer.get());
    MeshPtr& mesh = m_MeshCache[str];
    if(!mesh)
    {
        size_t n = str.rfind('.');
        if(n == std::string::npos)
            THROW_EXCEPTION("Bad format");
        LoadersMap::iterator il = m_MeshLoaders.find(str.substr(n));
        if(il == m_MeshLoaders.end())
            THROW_EXCEPTION("Unknown mesh format");
        mesh = il->second->load(m_ResourceCache->getResource(str), *this);
    }
    return mesh;
}

void ResourceManager::clear()
{
    m_MeshCache.clear();
    m_TexCache.clear();
}

void ResourceManager::registerMesh(const string& name, const MeshPtr& mesh)
{
    m_MeshCache[name] = mesh;
}

void ResourceManager::unregisterMesh(const string& name, bool all_ref)
{
    MeshCache::iterator iter = m_MeshCache.find(name);
    if(iter == m_MeshCache.end())
        THROW_EXCEPTION("Unknown mesh: " + name);
    MeshPtr mesh = iter->second;
    m_MeshCache.erase(iter);
    if(all_ref)
        for(MeshCache::iterator i = m_MeshCache.begin(); i != m_MeshCache.end();)
        {
            if(i->second == mesh)
                m_MeshCache.erase(i++);
            else
                ++i;
        }
}

size_t ResourceManager::getMeshExternalRefCount(MeshPtr& mesh) const
{
    size_t int_count = 0;
    for(MeshCache::const_iterator i = m_MeshCache.begin(); i != m_MeshCache.end(); ++i)
        if(i->second == mesh)
            ++int_count;
    return mesh.use_count()-int_count;
}

void ResourceManager::registerMeshLoader(const string& ext, const MeshLoaderPtr& mesh_loader)
{
    m_MeshLoaders[ext] = mesh_loader;
}

MeshLoaderPtr ResourceManager::getMeshFileLoader(const string& filename)
{
    size_t n = filename.rfind('.');
    if(n == std::string::npos)
        return MeshLoaderPtr();
    string ext(filename.substr(n));
    LoadersMap::iterator il = m_MeshLoaders.find(ext);
    if(il == m_MeshLoaders.end())
        return MeshLoaderPtr();
    return il->second;
}
}

